#include <inc/mmu.h>

# Start the CPU: switch to 32-bit protected mode, jump into C.
# The BIOS loads this code from the first sector of the hard disk into
# memory at physical address 0x7c00 and starts executing in real mode
# with %cs=0 %ip=7c00.

# 这些带`.`的汇编指令，其实是伪指令，是用来给汇编器看的
# 比如下面的.set 其实类似于C中的#define，set之后CR0_PE_ON 就是0x1了
.set PROT_MODE_CSEG, 0x8         # kernel code segment selector
.set PROT_MODE_DSEG, 0x10        # kernel data segment selector
.set CR0_PE_ON,      0x1         # protected mode enable flag

# .globl 表明外部符号，在链接时可以用到
.globl start
start:
  .code16                     # Assemble for 16-bit mode
  cli                         # Disable interrupts 禁用中断
  # clear direction flag,清除方向标志，默认的方向是往高地址方向
  # 还有一个std,将方向设置为低地址方向
  cld                         # String operations increment

  # Set up the important data segment registers (DS, ES, SS).
  xorw    %ax,%ax             # Segment number zero
  movw    %ax,%ds             # -> Data Segment
  movw    %ax,%es             # -> Extra Segment
  movw    %ax,%ss             # -> Stack Segment

  # 启用第21根地址线
  # 这样就能增加可使用（可寻址）的内存空间啦
  # 具体内容见 note/lab1-A20.md
  # Enable A20:
  #   For backwards compatibility with the earliest PCs, physical
  #   address line 20 is tied low, so that addresses higher than
  #   1MB wrap around to zero by default.  This code undoes this.
seta20.1:
  # 这是在等谁呢？
  # 等键盘控制器。
  # 启用A20竟然要用到键盘? 是键盘控制器，不是键盘
  # 不是说电脑没插键盘就启动不了，键盘控制器是连接键盘和主机的一个设备。
  # 0x64是键盘控制器的端口之一，keyboard controller还有一个端口0x60
  # 0x64是command port, 0x60是data port
  # 从0x64读取的1B的bit 1(从0开始)表示这个controller是否busy，1则busy
  # 具体的见https://wiki.osdev.org/%228042%22_PS/2_Controller 
  # 所以下面的代码可以等到controller空闲时
  inb     $0x64,%al               # Wait for not busy
  testb   $0x2,%al
  jnz     seta20.1

  # 同样，看osdev可以知道，把0xd1送到0x64端口，表示
  # 下一个byte将送往 Controller Output port
  movb    $0xd1,%al               # 0xd1 -> port 0x64
  outb    %al,$0x64

seta20.2:
  inb     $0x64,%al               # Wait for not busy
  testb   $0x2,%al
  jnz     seta20.2

  # 查osdev，只要bit 1为1即可启用A20
  # 0xdf = 10111111b  ->  bit 1为1
  movb    $0xdf,%al               # 0xdf -> port 0x60
  outb    %al,$0x60

  # Switch from real to protected mode, using a bootstrap GDT
  # and segment translation that makes virtual addresses
  # identical to their physical addresses, so that the
  # effective memory map does not change during the switch.
  # 此处已经启用了可用的32根地址线，下面有切换到保护模式，
  # 切换到保护模式的步骤在Intel 手册的Volume 3里面的9.9
  # 下面简单叙述
  # 1. 禁用中断
  #   最上面的cli已经做了
  # 2. 加载GDT
  # 3. 向控制寄存器(Control register)cr0发送切换指令
  #   note: Random failures can occur if other instructions exist between steps 3 and 4
  # 4. 发送之后，立即执行jmp或call操作
  # :
  # :
  # 一共有11步，并非全部要做，由于现在只是个bootstrap版本的jos，在此文件中只涉及到了前四步

  # 第2步
  # 从gdtdesc中加载GDT
  # 这条指令将会把目标地址中的6个byte读入
  # 这6个byte中的前2个byte是limit，表示GDT的大小：最大能达到起始地址+limit（inclusive）
  # 后4byte是GDT的线性地址
  lgdt    gdtdesc

  # 第3步，查手册可以发现0x1就是启用保护模式的
  movl    %cr0, %eax
  orl     $CR0_PE_ON, %eax
  movl    %eax, %cr0

  # 第4步，立即跳转
  # Jump to next instruction, but in 32-bit code segment.
  # Switches processor into 32-bit mode.
  ljmp    $PROT_MODE_CSEG, $protcseg

  .code32                     # Assemble for 32-bit mode
protcseg:
  # Set up the protected-mode data segment registers
  movw    $PROT_MODE_DSEG, %ax    # Our data segment selector
  movw    %ax, %ds                # -> DS: Data Segment
  movw    %ax, %es                # -> ES: Extra Segment
  movw    %ax, %fs                # -> FS
  movw    %ax, %gs                # -> GS
  movw    %ax, %ss                # -> SS: Stack Segment

  # 上面这段mov代码，在没有切换的时候反汇编如下
  # 0x00007c32:  66 b8 10 00 8e d8       mov    $0xd88e0010,%eax
  # 0x00007c38:  8e c0   mov    %ax,%es
  # 0x00007c3a:  8e e0   mov    %ax,%fs
  # 0x00007c3c:  8e e8   mov    %ax,%gs
  # 0x00007c3e:  8e d0   mov    %ax,%ss
  # 你可能会奇怪，给ds赋值的指令去哪了，别着急，先看一下opcode
  # 有没有发现写的汇编代码是给ax赋值，可反汇编的时候却是给eax赋值
  # 没错，就是给ax赋值和给ds赋值的指令"在一起了"
  # 其实不是真的在一起，如果你一直单步执行下去，你会发现所有上面的mov指令
  # 都如写在本文件中的那样被执行了
  #
  # 解释一下：
  # 1. 0x66前缀，有一个概念叫做操作数大小，当在16位模式下运行时
  # 默认的操作数大小为16位，当在32位模式下运行时默认操作数大小为32位
  # 如果一个指令加上了0x66前缀（比如上面的movw    $PROT_MODE_DSEG, %ax）
  # 那么这表示这条指令的操作数大小被反转：如果默认是16为操作数，那么这条指令的操作数是32位，
  #     如果默认为32位，那么这条指令操作数是16位
  # 2. 在还没有切换到32位的时候，你对上面的代码进行反汇编，
  # 此时默认的操作数是16位，反汇编时看到opcode(0xb8) 前面有0x66前缀，
  # 所以它就认为这个指令的操作数是32位的，于是就把opcode后面4个字节当成操作数
  # 而opcode后面的这4个byte的内容是$PROT_MODE_DSEG(两个字节)和movw %ax, %ds（这个指令占两个字节）
  # 可以看到给ds赋值的指令被反汇编器当成操作数了
  # 但是程序还是可以正确运行的，因为执行movw这段指令时是32位，而因为有66前缀
  # 这将正确地解释为我们希望的16位操作数



  # Set up the stack pointer and call into C.
  movl    $start, %esp
  call bootmain

  # If bootmain returns (it shouldn't), loop.
spin:
  jmp spin

# Bootstrap GDT
.p2align 2                                # force 4 byte alignment
gdt:
  # GDT表的第一个项是null pointer
  SEG_NULL				# null seg
  # STA_X = 0x8
  # STA_R = 0x2
  # STA_W = 0x2
  # 这三个都定义在inc/mmu.h中
  # 下面这两个是GDT中的两个项，即descriptor，参考volume3 3.4.5
  # 每一个segment descriptor 占64bit
  # 这三个部分分别是type, base, lim
  SEG(STA_X|STA_R, 0x0, 0xffffffff)	# code seg
  SEG(STA_W, 0x0, 0xffffffff)	        # data seg

gdtdesc:
  .word   0x17                            # sizeof(gdt) - 1
  .long   gdt                             # address gdt
